package com.lidroid.kernel;

import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class KernelReflect {

    /**
     * @param member
     * @return
     */
    public static <T extends Member> T memberStatic(T member) {
        return memberStatic(member, null);
    }

    /**
     * @param member
     * @param defaultValue
     * @return
     */
    public static <T extends Member> T memberStatic(T member, T defaultValue) {
        if (member == null || !Modifier.isStatic(member.getModifiers())) {
            return defaultValue;
        }

        return member;
    }

    /**
     * @param member
     * @return
     */
    public static <T extends Member> T memberAccessor(T member) {
        return memberAccessor(member, null);
    }

    /**
     * @param member
     * @param defaultValue
     * @return
     */
    public static <T extends Member> T memberAccessor(T member, T defaultValue) {
        if (member == null || Modifier.isFinal(member.getModifiers()) || Modifier.isStatic(member.getModifiers())) {
            return defaultValue;
        }

        return member;
    }

    /**
     * @param cls
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> constructor(Class<T> cls, Class... parameterTypes) {
        return declaredConstructor(cls, false, parameterTypes);
    }

    /**
     * @param cls
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> declaredConstructor(Class<T> cls, Class... parameterTypes) {
        return declaredConstructor(cls, true, parameterTypes);
    }

    /**
     * @param cls
     * @param declared
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> declaredConstructor(Class<T> cls, boolean declared, Class... parameterTypes) {
        try {
            Constructor<T> constructor = declared ? cls.getDeclaredConstructor(parameterTypes) : cls.getConstructor(parameterTypes);
            if (constructor != null) {
                if (declared && !constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }

                return constructor;
            }

        } catch (SecurityException e) {
        } catch (NoSuchMethodException e) {
        }

        return null;
    }

    /**
     * @param cls
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> assignableConstructor(Class<T> cls, Class... parameterTypes) {
        return assignableConstructor(cls, false, parameterTypes);
    }

    /**
     * @param cls
     * @param declared
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> assignableConstructor(Class<T> cls, boolean declared, Class... parameterTypes) {
        Constructor<T>[] constructors = (Constructor<T>[]) (declared ? cls.getDeclaredConstructors() : cls.getConstructors());
        int similar = -2;
        Constructor<T> constructor = null;
        for (Constructor<T> structor : constructors) {
            if (KernelClass.isMatchableFrom(structor.getParameterTypes(), parameterTypes)) {
                if (!structor.isAccessible()) {
                    if (declared) {
                        structor.setAccessible(true);

                    } else {
                        continue;
                    }
                }

                int sim = KernelClass.similar(parameterTypes, structor.getParameterTypes());
                if (similar < sim) {
                    similar = sim;
                    constructor = structor;
                }
            }
        }

        return constructor;
    }

    /**
     * @param cls
     * @param declared
     * @param assignable
     * @param parameterTypes
     * @return
     */
    public static <T> Constructor<T> assignableConstructor(Class<T> cls, boolean declared, boolean assignable, Class... parameterTypes) {
        return assignable ? assignableConstructor(cls, declared, parameterTypes) : declaredConstructor(cls, declared, parameterTypes);
    }

    /**
     * @param constructor
     * @param initargs
     * @return
     */
    public static <T> T newInstance(Constructor<T> constructor, Object... initargs) {
        return newInstance(constructor, null, initargs);
    }

    /**
     * @param constructor
     * @param defaultValue
     * @param initargs
     * @return
     */
    public static <T> T newInstance(Constructor<T> constructor, T defaultValue, Object... initargs) {
        if (constructor != null) {
            try {
                return constructor.newInstance(initargs);

            } catch (IllegalArgumentException e) {
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }

        return defaultValue;
    }

    /**
     * @param cls
     * @param name
     * @return
     */
    public static Field field(Class cls, String name) {
        return field(cls, name, 0);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @return
     */
    public static Field field(Class cls, String name, int ancest) {
        return declaredField(cls, name, ancest, false);
    }

    /**
     * @param cls
     * @param name
     * @return
     */
    public static Field declaredField(Class cls, String name) {
        return declaredField(cls, name, 0);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @return
     */
    public static Field declaredField(Class cls, String name, int ancest) {
        return declaredField(cls, name, ancest, true);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param declared
     * @return
     */
    public static Field declaredField(Class cls, String name, int ancest, boolean declared) {
        Field field = null;
        while (cls != null) {
            try {
                field = declared ? cls.getDeclaredField(name) : cls.getField(name);
                break;

            } catch (SecurityException e) {
                break;

            } catch (NoSuchFieldException e) {
            }

            if (ancest < 1 || ancest-- != 1) {
                cls = cls.getSuperclass();
            } else {
                cls = null;
            }
        }

        if (field != null) {
            if (declared && !field.isAccessible()) {
                field.setAccessible(true);
            }
        }

        return field;
    }

    /**
     * @param cls
     * @return
     */
    public static List<Field> fields(Class cls) {
        return fields(cls, 0);
    }

    /**
     * @param cls
     * @param ancest
     * @return
     */
    public static List<Field> fields(Class cls, int ancest) {
        return declaredFields(cls, ancest, false);
    }

    /**
     * @param cls
     * @return
     */
    public static List<Field> declaredFields(Class cls) {
        return declaredFields(cls, 0);
    }

    /**
     * @param cls
     * @param ancest
     * @return
     */
    public static List<Field> declaredFields(Class cls, int ancest) {
        return declaredFields(cls, ancest, true);
    }

    /**
     * @param cls
     * @param ancest
     * @param declared
     * @return
     */
    public static List<Field> declaredFields(Class cls, int ancest, boolean declared) {
        List<Field> declaredFields = new ArrayList<Field>();
        while (cls != null) {
            Field[] fields = declared ? cls.getDeclaredFields() : cls.getFields();
            for (Field field : fields) {
                if (declared && !field.isAccessible()) {
                    field.setAccessible(true);
                }

                declaredFields.add(field);
            }

            if (ancest < 1 || ancest-- != 1) {
                cls = cls.getSuperclass();

            } else {
                cls = null;
            }
        }

        return declaredFields;
    }

    /**
     * @param obj
     * @param field
     * @param value
     * @return
     */
    public static boolean set(Object obj, Field field, Object value) {
        if (field != null) {
            try {
                field.setAccessible(true);
                field.set(obj, value);
                return true;
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }

        return false;
    }

    /**
     * @param obj
     * @param field
     * @return
     */
    public static Object get(Object obj, Field field) {
        return KernelReflect.get(obj, null, field);
    }

    /**
     * @param obj
     * @param defaultValue
     * @param field
     * @return
     */
    public static Object get(Object obj, Object defaultValue, Field field) {
        if (field != null) {
            try {
                return field.get(obj);

            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }

        return defaultValue;
    }

    /**
     * Class_Method_Map_Method
     */
    protected final static Map<String, Method> Class_Method_Map_Method = new HashMap<String, Method>();

    /**
     * @param cls
     * @param name
     * @param parameterTypes
     * @return
     */
    public static Method method(Class cls, String name, Class... parameterTypes) {
        return method(cls, name, 0, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param parameterTypes
     * @return
     */
    public static Method method(Class cls, String name, int ancest, Class... parameterTypes) {
        return declaredMethod(cls, name, ancest, false, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param parameterTypes
     * @return
     */
    public static Method declaredMethod(Class cls, String name, Class... parameterTypes) {
        return declaredMethod(cls, name, 0, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param parameterTypes
     * @return
     */
    public static Method declaredMethod(Class cls, String name, int ancest, Class... parameterTypes) {
        return declaredMethod(cls, name, ancest, true, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param declared
     * @param parameterTypes
     * @return
     */
    public static Method declaredMethod(Class cls, String name, int ancest, boolean declared, Class... parameterTypes) {
        return assignableMethod(cls, name, ancest, declared, false, false, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param parameterTypes
     * @return
     */
    public static Method assignableMethod(Class cls, String name, Class... parameterTypes) {
        return assignableMethod(cls, name, 0, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param parameterTypes
     * @return
     */
    public static Method assignableMethod(Class cls, String name, int ancest, Class... parameterTypes) {
        return assignableMethod(cls, name, ancest, false, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param declared
     * @param parameterTypes
     * @return
     */
    public static Method assignableMethod(Class cls, String name, int ancest, boolean declared, Class... parameterTypes) {
        return assignableMethod(cls, name, ancest, declared, true, true, parameterTypes);
    }

    /**
     * @param cls
     * @param name
     * @param ancest
     * @param declared
     * @param assignable
     * @param cacheable
     * @param parameterTypes
     * @return
     */
    public static Method assignableMethod(Class cls, final String name, int ancest, boolean declared, boolean assignable, boolean cacheable, final Class... parameterTypes) {
        String class_method_key = null;
        if (cacheable) {
            class_method_key = cls.getName() + ":" + name + ":" + parameterTypes.length;
            Method method = Class_Method_Map_Method.get(class_method_key);
            if (method != null && KernelClass.isMatchableFrom(method.getParameterTypes(), parameterTypes)) {
                return method;
            }
        }

        Method method = null;
        while (cls != null) {
            if (assignable && parameterTypes.length > 0) {
                final List<Method> matchMethods = new ArrayList<Method>();
                KernelLang.CallbackBreak<Method> callback = new KernelLang.CallbackBreak<Method>() {

                    @Override
                    public void doWith(Method template) throws KernelLang.BreakException {
                        // TODO Auto-generated method stub
                        if (template.getName().equals(name) && KernelClass.isMatchableFrom(template.getParameterTypes(), parameterTypes)) {
                            for (Method method : matchMethods) {
                                if (KernelArray.equal(method.getParameterTypes(), template.getParameterTypes())) {
                                    return;
                                }
                            }

                            matchMethods.add(template);
                        }
                    }

                };

                if (declared) {
                    doWithDeclaredMethods(cls, callback);

                } else {
                    doWithMethods(cls, callback);
                }

                if (matchMethods.size() > 0) {
                    if (matchMethods.size() == 1) {
                        method = matchMethods.get(0);

                    } else {
                        cacheable = false;
                        int similar = -2;
                        for (Method match : matchMethods) {
                            int sim = KernelClass.similar(parameterTypes, match.getParameterTypes());
                            if (similar < sim) {
                                similar = sim;
                                method = match;
                            }
                        }
                    }
                }

            } else {
                try {
                    method = declared ? cls.getDeclaredMethod(name, parameterTypes) : cls.getMethod(name, parameterTypes);
                    break;

                } catch (SecurityException e) {
                    break;

                } catch (NoSuchMethodException e) {
                }
            }

            if (ancest < 1 || ancest-- != 1) {
                cls = cls.getSuperclass();

            } else {
                cls = null;
            }
        }

        if (method != null) {
            if (declared && !method.isAccessible()) {
                method.setAccessible(true);
            }

            if (cacheable) {
                Class_Method_Map_Method.put(class_method_key, method);
            }
        }

        return method;
    }

    /**
     * @param obj
     * @param method
     * @param args
     * @return
     */
    public static Object invoke(Object obj, Method method, Object... args) {
        return KernelReflect.invoke(obj, null, method, args);
    }

    /**
     * @param obj
     * @param defaultValue
     * @param method
     * @param args
     * @return
     */
    public static Object invoke(Object obj, Object defaultValue, Method method, Object... args) {
        if (method != null) {
            try {
                return method.invoke(obj, args);
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }

        return defaultValue;
    }

    /**
     * @param cls
     * @param callback
     */
    public static void doWithClasses(Class cls, KernelLang.CallbackBreak<Class<?>> callback) {
        doWithClasses(cls, callback, null);
    }

    /**
     * @param cls
     * @param callback
     * @param superClass
     */
    public static void doWithClasses(Class cls, KernelLang.CallbackBreak<Class<?>> callback, Class superClass) {
        try {
            while (cls != null && cls != Object.class && cls != superClass) {
                callback.doWith(cls);
                cls = cls.getSuperclass();
            }
        } catch (KernelLang.BreakException e) {
        }
    }

    /**
     * @param cls
     * @param callback
     */
    public static void doWithFields(Class cls, KernelLang.CallbackBreak<Field> callback) {
        doWithFields(cls, callback, null);
    }

    /**
     * @param cls
     * @param callback
     * @param superClass
     */
    public static void doWithFields(Class cls, KernelLang.CallbackBreak<Field> callback, Class superClass) {
        try {
            while (cls != null && cls != Object.class && cls != superClass) {
                for (Field field : cls.getFields()) {
                    callback.doWith(field);
                }

                cls = cls.getSuperclass();
            }
        } catch (KernelLang.BreakException e) {
        }
    }

    /**
     * @param cls
     * @param callback
     */
    public static void doWithDeclaredFields(Class cls, KernelLang.CallbackBreak<Field> callback) {
        doWithDeclaredFields(cls, callback, null);
    }

    /**
     * @param cls
     * @param callback
     * @param superClass
     */
    public static void doWithDeclaredFields(Class cls, KernelLang.CallbackBreak<Field> callback, Class superClass) {
        try {
            while (cls != null && cls != Object.class && cls != superClass) {
                for (Field field : cls.getDeclaredFields()) {
                    callback.doWith(field);
                }

                cls = cls.getSuperclass();
            }
        } catch (KernelLang.BreakException e) {
        }
    }

    /**
     * @param cls
     * @param callback
     */
    public static void doWithMethods(Class cls, KernelLang.CallbackBreak<Method> callback) {
        doWithMethods(cls, callback, null);
    }

    /**
     * @param cls
     * @param callback
     * @param superClass
     */
    public static void doWithMethods(Class cls, KernelLang.CallbackBreak<Method> callback, Class superClass) {
        try {
            while (cls != null && cls != Object.class && cls != superClass) {
                for (Method method : cls.getMethods()) {
                    callback.doWith(method);
                }

                cls = cls.getSuperclass();
            }
        } catch (KernelLang.BreakException e) {
        }
    }

    /**
     * @param cls
     * @param callback
     */
    public static void doWithDeclaredMethods(Class cls, KernelLang.CallbackBreak<Method> callback) {
        doWithDeclaredMethods(cls, callback, null);
    }

    /**
     * @param cls
     * @param callback
     * @param superClass
     */
    public static void doWithDeclaredMethods(Class cls, KernelLang.CallbackBreak<Method> callback, Class superClass) {
        try {
            while (cls != null && cls != Object.class && cls != superClass) {
                for (Method method : cls.getDeclaredMethods()) {
                    callback.doWith(method);
                }

                cls = cls.getSuperclass();
            }

        } catch (KernelLang.BreakException e) {

        }
    }
}
